<template>
  <div
    ref="codeEditor"
    class="hello"
    style="width:100%;height:100%;"
  >
    <textarea
      ref="codeJson"
      name="code-json"
      style="width:100%;height:100%;"
    />
  </div>
</template>

<script>
import CodeMirror from 'codemirror/lib/codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/mode/shell/shell';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/vue/vue.js';

import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/javascript-hint';
import 'codemirror/addon/display/placeholder.js';

// 高亮行功能
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/selection/selection-pointer';
import { shallowRef } from 'vue';

export default {
  name: 'CodeEditor',
  props: {
    modelValue: {
      type: String,
      default: '',
    },
    tipType: {
      type: String,
      default: '1',
    },
    methodSelect: {
      type: Boolean,
      default: false,
    },
    propertySelect: {
      type: Boolean,
      default: true,
    },
    widgetRef: {
      type: String,
      default: '',
    },
    editorHeight: {
      type: String,
      default: '',
    },
    editorMinHeight: {
      type: String,
      default: '',
    },
    editorWidth: {
      type: String,
      default: '100%',
    },
    placeHolder: {
      type: String,
      default: '',
    },
  },
  emits: ['update:modelValue'],
  data() {
    return {
      cmOptions: {
        mode: 'text/javascript',
        theme: 'idea',
        line: true,
        lineWrapping: true,
        autofocus: true,

        // 高亮行功能
        styleActiveLine: true,
        hintOptions: {
          completeSingle: false,
          hint: this.handleShowHint,
        },
      },
      editor: null,
      hintList: [],
      placeText: `{{${this.placeHolder.replace('{{', '').replace('}}', '')}}}`,
      minHeight: this.editorMinHeight || this.editorHeight || 60,
      height: `${this.editorHeight}px` || '100%',
      isExistPlaceText: false,
    };
  },
  created() {
    const dataStructs = window.xVueWeb.app.config.globalProperties.$xvueDataStructs.sort((item1, item2) => {
      if (item1.name > item2.name) {
        return 1;
      }
      if (item1.name < item2.name) {
        return -1;
      }
      if (item1.name === item2.name) {
        return 0;
      }
      return -1;
    });
    this.hintList = JSON.parse(JSON.stringify(dataStructs));

    // 只提示注册了方法的组件
    if (!this.propertySelect && this.methodSelect) {
      this.dissHintOnlyExistProperty(this.hintList);
    }

    // 只提示注册了属性的组件
    if (this.propertySelect && !this.methodSelect) {
      this.dissHintOnlyExistMethod(this.hintList);
    }

    // 去掉即没有注册方法，又没有注册属性的组件
    this.disEmptyHint(this.hintList);
  },
  mounted() {
    this.$refs.codeEditor.style.setProperty('--code-editor-height', `${this.editorHeight}`);
    this.$refs.codeEditor.style.setProperty('--code-editor-min-height', `${this.minHeight}px`);
    this.$refs.codeEditor.style.setProperty('--code-editor-width', `${this.editorWidth}${this.editorWidth === '100%' ? '' : 'px'}`);

    this.editor = shallowRef(CodeMirror.fromTextArea(this.$refs.codeJson, this.cmOptions));
    this.editor.setOption('placeholder', this.placeText);
    if (this.modelValue) {
      this.editor.setValue(this.modelValue);
    }

    // 代码自动提示功能，记住使用cursorActivity事件不要使用change事件，这是一个坑，那样页面直接会卡死
    this.editor.on('cursorActivity', () => {
      this.editor.showHint();
    });
    this.editor.on('change', (coder, changeObj) => {
      const cursor = coder.getCursor();

      // 得到行内容
      const val = coder.getLine(cursor.line);

      if (val.charAt(changeObj.to.ch - 1) === '.') {
        this.$emit('update:modelValue', val);
        return;
      }

      if (changeObj.origin === 'complete') {
        const completeTxt = changeObj.text.join('');
        for (let j = 0; j < completeTxt.length; j++) {
          coder.execCommand('goCharLeft');
        }
        for (let i = 0; i < 200; i++) {
          if (val.charAt(coder.getCursor().ch - 1) === ' ' ||
              val.charAt(coder.getCursor().ch - 1) === '{' ||
              val.charAt(coder.getCursor().ch - 1) === '=' ||
              val.charAt(coder.getCursor().ch - 1) === '>') {
            break;
          } else {
            coder.execCommand('delCharBefore');
          }
        }

        for (let i = 0; i < completeTxt.length; i++) {
          coder.execCommand('goCharRight');
        }
      }
      this.$emit('update:modelValue', val);
    });
    this.editor.on('blur', () => {
      setTimeout(() => {
        window.xVueWeb.app.config.globalProperties.$adoPremiseEvtBus.$emit('code-mirror-editor-blur');
      }, 0);
    });
    this.editor.on('inputRead', (cm, name, event) => {
      const val = cm.getValue();
      const reg = /[\&|\<|\>]/g;
      if (reg.test(val.charAt(val.length - 1))) {
        cm.undo();
      }
    });
    window.xVueWeb.app.config.globalProperties.$adoPremiseEvtBus.$on('tab-property-panel-change', () => {
      setTimeout(() => {
        this.editor.refresh();
      }, 0);
    });
  },
  beforeUnmount() {
    window.xVueWeb.app.config.globalProperties.$adoPremiseEvtBus.$off('tab-property-panel-change');
    this.editor.toTextArea();
  },
  methods: {
    dissHintOnlyExistMethod(hints) {
      for (let i = 0; i < hints.length; i++) {
        const hint = hints[i];
        const properties = hint.value.filter(item => !item.type);
        const methods = hint.value.filter(item => item.type === 'action');
        if (!properties.length && methods.length) {
          hints.splice(i, 1);
          i--;
        }
      }
    },
    dissHintOnlyExistProperty(hints) {
      for (let i = 0; i < hints.length; i++) {
        const hint = hints[i];
        const methods = hint.value.filter(item => item.type === 'action');
        if (!methods.length) {
          hints.splice(i, 1);
          i--;
        }
      }
    },
    disEmptyHint(hints) {
      for (let i = 0; i < hints.length; i++) {
        const hint = hints[i];
        if (!hint.value.length) {
          hints.splice(i, 1);
          i--;
        }
      }
    },
    handleShowHint() {
      const cmInstance = this.editor;

      // 得到光标
      const cursor = cmInstance.getCursor();

      // 得到行内容
      const cursorLine = cmInstance.getLine(cursor.line);

      // 得到光标位置
      const end = cursor.ch;
      const start = end;
      const one = `${cursorLine.charAt(start - 1)}`;
      let list = [];
      if (cursorLine.indexOf('{{') > -1) {
        const lastIndex = cursorLine.lastIndexOf('{{', start);
        const tip = cursorLine.substring(lastIndex + 2, end);
        if (tip.indexOf('=') < 0) {
          this.hintList.forEach(e => {
            if (e.name.startsWith(tip) && this.tipType === '1') {
              // 都提示
              list.push(e.name);
            } else if (e.name.startsWith(tip) && e.name === this.widgetRef && this.tipType === '2') {
              // 显示自己，排除其它
              list.push(e.name);
            } else if (e.name.startsWith(tip) && e.name !== this.widgetRef && this.tipType === '3') {
              // 显示其它，排除自己
              list.push(e.name);
            } else {}
          });
        } else {
          const lastIndex1 = cursorLine.lastIndexOf('=', start);
          const tip1 = cursorLine.substring(lastIndex1 + 1, start).replaceAll(' ', '');
          this.hintList.forEach(e => {
            if (e.name.startsWith(tip1) && e.name !== this.widgetRef) {
              // 显示其它，排除自己
              list.push(e.name);
            }
          });
        }
      }
      if (one === '.' ) {
        const lastIndex2 = cursorLine.lastIndexOf('{{', start);
        let keyStr = cursorLine.substring(lastIndex2 + 2, start - 1);
        if (keyStr.indexOf('=') > -1) {
          keyStr = keyStr.split('=')[1].replaceAll(' ', '');
        }
        const keys = keyStr.split('.');

        list = [];
        let hintEl = null;

        for (let i = 0; i < keys.length; i++) {
          const key = keys[i];
          if (i === 0) {
            hintEl = this.hintList.find(item => item.name === key.trim());
          }

          if (i > 0 && i <= keys.length - 1) {
            if (key.indexOf('[') > -1) {
              const reg = /^(\w+)(\[)(\d+)(\])$/g;
              reg.test(key);
              hintEl = hintEl.value.find(item => item.name.indexOf(`${RegExp.$1 + RegExp.$2}0${RegExp.$4}`) > -1);
            } else {
              hintEl = hintEl.value.find(item => item.name === key);
            }
          }

          if (i === keys.length - 1) {
            if (hintEl && Object.prototype.toString.call(hintEl.value) === '[object Array]') {
              hintEl.value.forEach(el => {
                const isJHMethodSelect = cursorLine.indexOf('#') > -1 && el.type && el.type === 'action';
                if (isJHMethodSelect) {
                  list.push(el.name);
                }
                const isKhMethodSelect = cursorLine.indexOf('{{') > -1 && this.methodSelect && el.type && el.type === 'action';
                if (isKhMethodSelect) {
                  list.push(el.name);
                }
                const isPropertySelect = cursorLine.indexOf('{{') > -1 && (!el.type || el.type !== 'action') && this.propertySelect;
                if (isPropertySelect) {
                  if (cursorLine.indexOf('global') > 0) {
                    list.push(el.name);
                  } else {
                    if (cursorLine.indexOf('=') < 0 && this.tipType !== '3' && el.name !== 'variable') {
                      list.push(el.name);
                    }
                    if ((cursorLine.indexOf('=') > -1 || this.tipType === '3') && (el.name !== 'variable' || el.name !== 'param')) {
                      list.push(el.name);
                    }
                  }
                }
              });
            }
          }
        }
      } else {}

      // 得到光标标识
      const token = cmInstance.getTokenAt(cursor);
      return {
        list: list,
        from: { ch: end, line: cursor.line },
        to: { ch: token.end, line: cursor.line },
      };
    },
  },
};
</script>
<style scoped lang="less">
/deep/ .CodeMirror {
  border: 1px solid lightgray;
  height: var(--code-editor-height) !important;
  min-height: var(--code-editor-min-height) !important;
  width: var(--code-editor-width) !important;
}
/deep/ .CodeMirror .CodeMirror-vscrollbar {
  width: 1px;
  border: 1px solid lightgray;
}
/deep/ .cm-s-idea .CodeMirror-activeline-background {
  background-color: transparent;
}
/deep/ .CodeMirror-placeholder {
  color: #aaa !important;
}
</style>
